/*
 *  Getris -- A Tetris game written in Groovy.
 *  Copyright (C) 2008 Johnny Jian
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
package cn.johnnyjian.getris.panel

import cn.johnnyjian.getris.game.color.ColorfulContainer
import cn.johnnyjian.getris.game.score.ScoreKeeper
import java.awt.Color
import java.awt.event.ActionListener
import java.awt.event.KeyAdapter
import java.awt.event.KeyEvent
import javax.swing.Timer
import java.awt.Graphics
import java.awt.Font

class TetrisPanel extends SquarePanel {

	private static final NOTICE_WIDTH = 180
	private static final NOTICE_HEIGHT = 50
	private static final NOTICE_FONT = new Font('SansSerif', Font.BOLD, 26)
	private static final NOTICE_COLOR = new Color(240, 240, 240)

	def observable
	def queueSize
	def container
	def scoreKeeper
	def timer
	def state

	TetrisPanel(width, height, size, queue) {
		super(width, height, size)
		queueSize = queue
		observable = new Observable()
		state = GameState.STOPPED
		timer = new DropPaceTimer(this, dropPace)

		focusable = true
		addKeyListener([keyPressed: keyPressed] as KeyAdapter)
	}

	def methodMissing(String name, args) {
		Observable.metaClass.invokeMethod(observable, name, args)
	}

	def moveLeft = {
		container.droppingQuadrel.moveLeft()
	}

	def moveRight = {
		container.droppingQuadrel.moveRight()
	}

	def rotate = {
		container.droppingQuadrel.rotate()
	}

	def dropPace = {
		if (!running) {
			return
		}
		container.droppingQuadrel.dropPace()
		if (container.droppingQuadrel.reachedBottom) {
			setNextScene()
		}
	}

	def dropDown = {
		container.droppingQuadrel.dropDown()
		setNextScene()
	}

	def keyMap = [
	    (KeyEvent.VK_LEFT): moveLeft,
		(KeyEvent.VK_RIGHT): moveRight,
		(KeyEvent.VK_UP): rotate,
		(KeyEvent.VK_DOWN): dropPace,
		(KeyEvent.VK_SPACE): dropDown,

		(KeyEvent.VK_A): moveLeft,
		(KeyEvent.VK_D): moveRight,
		(KeyEvent.VK_J): rotate,
		(KeyEvent.VK_S): dropPace,
		(KeyEvent.VK_K): dropDown,
	]

	def keyPressed = { e ->
		if (!running) {
			return
		}
		if (keyMap.containsKey(e.keyCode)) {
			keyMap[e.keyCode]()
		}
	}

	boolean isStopped() {
		state == GameState.STOPPED
	}

	boolean isRunning() {
		state == GameState.RUNNING
	}

	boolean isPaused() {
		state == GameState.PAUSED
	}

	private setNextScene() {
		container.removeFullLines()
		scoreKeeper.addLines(container.removedLineCount)
		container.dropNextQuadrel()
		notifyObservers()

		if (container.gameOver) {
			state = GameState.STOPPED
			repaint()
		}
	}

	private notifyObservers() {
		observable.setChanged()
		observable.notifyObservers([container: container, scoreKeeper: scoreKeeper])
	}

	def newGame() {
		container = new ColorfulContainer(containerWidth, containerHeight, queueSize)
		scoreKeeper = new ScoreKeeper()
		colors = new Color[containerHeight][containerWidth]

		container.addObserver({ o, arg -> drawDifferentSquares() } as Observer)
		container.startToDrop()
		state = GameState.RUNNING
		notifyObservers()

		repaint()
		requestFocus()
	}

	def resume() {
		state = GameState.RUNNING
		timer.restart()
		repaint()
		requestFocus()
	}

	def pause() {
		state = GameState.PAUSED
		timer.stop()
		repaint()
	}

	private drawDifferentSquares() {
		colors?.eachWithIndex { row, y ->
			row.eachWithIndex { c, x ->
				def color = container.getColorAt(x, y)
				if (c != color) {
					colors[y][x] = color
					drawSquareAt x, y
				}
			}
		}
	}

	private drawNotice(text, color, g) {
		g.font = NOTICE_FONT

		def noticeLeft = (width - NOTICE_WIDTH).intdiv(2)
		def noticeTop = (height - NOTICE_HEIGHT).intdiv(2) - 50
		def metrics = g.fontMetrics
		def textWidth = metrics.stringWidth(text)
		def textHeight = metrics.ascent + metrics.descent
		def textLeft = (NOTICE_WIDTH - textWidth).intdiv(2) + noticeLeft
		def textTop = (NOTICE_HEIGHT - textHeight).intdiv(2) + noticeTop + metrics.ascent

		g.color = NOTICE_COLOR
		g.fill3DRect noticeLeft, noticeTop, NOTICE_WIDTH, NOTICE_HEIGHT, true
		g.color = color
		g.drawString text, textLeft, textTop
	}

	void paint(Graphics g) {
		super.paint(g)
		if (paused) {
			drawNotice('PAUSED', Color.BLUE, g)
		}
		if (container?.gameOver) {
			drawNotice('GAME OVER', Color.RED, g)
		}
	}

}

enum GameState { STOPPED, RUNNING, PAUSED }

class DropPaceTimer {

	private static final INIT_DELAY = 1000
	private static final DELAY_REDUCE_FACTOR = 0.9

	def action
	def timer
	def level
	def delay

	DropPaceTimer(tetris, action) {
		this.action = action
		level = 0

		tetris.addObserver({ o, game ->
			def container = game.container
			def scoreKeeper = game.scoreKeeper
			if (container.gameOver) { // if the game is over, stop the timer
				level = 0
				stop()
			} else { // if a new quadrel is coming, restart the timer
				if (level != scoreKeeper.level) { // if level up, update the delay
					level = scoreKeeper.level
					delay = (INIT_DELAY * DELAY_REDUCE_FACTOR ** (level - 1)) as int
				}
				restart()
			}
		} as Observer)
	}

	def restart() {
		stop()
		timer = new Timer(delay, action as ActionListener)
		timer.start()
	}

	def stop() {
		timer?.stop()
	}

}
